package com.passer.toolbox.common.crawler;

import com.passer.toolbox.common.core.util.StringUtils;
import com.passer.toolbox.common.file.util.WriteFileUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.HttpClientUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * <p>文件下载爬虫</p>
 * <p>创建时间：2023/4/26</p>
 *
 * @author hj
 */
public class DownloadCrawler {
    // 生成httpclient，相当于该打开一个浏览器
    private final CloseableHttpClient httpClient = HttpClients.createDefault();
    // 创建get请求，相当于在浏览器地址栏输入网址
    private final HttpGet request;

    public DownloadCrawler() {
        this.request = new HttpGet();
    }

    public DownloadCrawler(String uri) {
        this.request = new HttpGet(uri);
    }

    public DownloadCrawler(URI uri, Header[] headers) {
        this.request = new HttpGet(uri);
        this.request.setHeaders(headers);
    }

    public DownloadCrawler(String uri, Header[] headers) {
        this.request = new HttpGet(uri);
        this.request.setHeaders(headers);
    }

    public URI getUri() {
        return this.request.getURI();
    }

    public void setUri(URI uri) {
        this.request.setURI(uri);
    }

    public void setUri(String url) {
        this.request.setURI(URI.create(url));
    }

    public Header[] getHeaders() {
        return this.request.getAllHeaders();
    }

    public void setHeaders(Header[] headers) {
        this.request.setHeaders(headers);
    }

    /**
     * 网络文件下载
     *
     * @param folderPath 文件保存目录
     * @throws IOException IO异常
     */
    public void downloadFile(String folderPath) throws IOException {
        File folder = new File(folderPath);
        if (!folder.exists()) {
            throw new RuntimeException("保存文件目录不存在！");
        }
        String[] strings = this.request.getURI().toString().split("/");
        String fileName = strings[strings.length - 1];
        fileName = URLDecoder.decode(fileName, "UTF-8");
        File file = new File(folder, fileName);
        downloadFile(file);
    }

    /**
     * 网络文件下载
     * @param folderPath 文件保存路径
     * @param fileName 文件名
     * @throws IOException IO异常
     */
    public void downloadFile(String folderPath, String fileName) throws IOException {
        File folder = new File(folderPath);
        if (!folder.exists()) {
            throw new RuntimeException("保存文件目录不存在！");
        }
        File file = new File(folder, fileName);
        downloadFile(file);
    }

    /**
     * 互联网文件下载
     * @param file 保存文件
     * @throws IOException IO异常
     */
    public void downloadFile(File file) throws IOException {

        //执行get请求，相当于在输入地址栏后敲回车键
        CloseableHttpResponse response = this.httpClient.execute(this.request);

        //4.判断响应状态为200，进行处理
        if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            //5.获取响应内容
            HttpEntity httpEntity = response.getEntity();

            InputStream content = httpEntity.getContent();
            WriteFileUtils.writeFileByStream(content, file);
        }
        //6.关闭
        HttpClientUtils.closeQuietly(response);
    }

    /**
     * 网络文件多线程下载
     * @param folderPath 文件保存目录
     * @param chunkNum 线程个数
     */
    public void segmentedDownload(String folderPath, int chunkNum) {
        if (chunkNum < 1) {
            throw new RuntimeException("分段个数必须大于一！");
        }
        File folder = new File(folderPath);
        if (!folder.exists()) {
            throw new RuntimeException("保存文件目录不存在！");
        }
        String[] strings = this.request.getURI().toString().split("/");
        String fileName = strings[strings.length - 1];
        try {
            fileName = URLDecoder.decode(fileName, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        File file = new File(folder, fileName);
        String chunkFolderName = fileName.substring(0, fileName.lastIndexOf('.'));
        File tempFolder = new File(folder, chunkFolderName);
        if (!tempFolder.exists() && !tempFolder.mkdirs()) {
            throw new RuntimeException("临时文件保存目录不存在且新建失败！");
        }
        long fileSize = getFileSize();
        if (fileSize < 1) {
            throw new RuntimeException("文件下载失败！");
        }
        List<File> chunkFileList = new ArrayList<>();
        AtomicBoolean hasError = new AtomicBoolean(false);
        ArrayList<String> errorMsgList = new ArrayList<>();
        long chunkSize = fileSize / chunkNum;
        for (long i = 0; i < fileSize; i += chunkSize) {
            if (hasError.get()) {
                break;
            }
            long startLength = i;
            long endLength = i + chunkSize;
            File chunkFile = new File(tempFolder, startLength + "");
            try {
                downloadFileChunk(chunkFile, startLength, endLength);
            } catch (IOException e) {
                System.out.println(chunkFile.getAbsolutePath() + " 下载失败！");
            }
            // Runnable runnable = () -> {
            //     try {
            //         downloadFileChunk(chunkFile, startLength, endLength);
            //     } catch (IOException e) {
            //         hasError.set(true);
            //         errorMsgList.add(e.getMessage());
            //         System.out.println(chunkFile.getAbsolutePath() + " 下载失败！");
            //         return;
            //     }
            //     chunkFileList.add(chunkFile);
            //     System.out.println(chunkFile.getAbsolutePath() + " 下载完成！");
            // };
            // Thread thread = new Thread(runnable);
            // thread.start();
        }

        // while(chunkFileList.size() < chunkNum) {
        //     if (hasError.get()) {
        //         throw new RuntimeException("文件下载失败：" + errorMsgList);
        //     }
        //     try {
        //         Thread.sleep(100);
        //     } catch (InterruptedException e) {
        //         e.printStackTrace();
        //     }
        // }
        // System.out.println("文件下载完成");


        // FileUtils.mergeFile(chunkFileList, file);
    }

    public void downloadFileChunk(File chunkFile, long startLength, long endLength) throws IOException {
        String rangeValue = "bytes=" + startLength + "-" + endLength;
        BasicHeader range = new BasicHeader("Range", rangeValue);
        this.request.addHeader(range);

        CloseableHttpResponse response = this.httpClient.execute(this.request);

        if (response.getStatusLine().getStatusCode() == HttpStatus.SC_PARTIAL_CONTENT) {
            HttpEntity httpEntity = response.getEntity();
            InputStream content = httpEntity.getContent();
            WriteFileUtils.writeFileByStream(content, chunkFile);
        }
        this.request.removeHeader(range);
        //6.关闭
        HttpClientUtils.closeQuietly(response);
    }


    /**
     * 获取文件大小，单位字节
     *
     * @return 文件大小
     */
    public long getFileSize() {
        long size = 0;
        //执行get请求，相当于在输入地址栏后敲回车键
        CloseableHttpResponse response;
        try {
            response = this.httpClient.execute(this.request);
        } catch (IOException e) {
            e.printStackTrace();
            return size;
        }

        //判断响应状态为200，进行处理
        if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            Header contentLength = response.getLastHeader("Content-Length");
            if (contentLength != null) {
                String value = contentLength.getValue();
                if (StringUtils.isNotEmpty(value)) {
                    size = Long.parseLong(value);
                }
            }
        }
        //关闭
        HttpClientUtils.closeQuietly(response);

        return size;
    }

    public void close() {
        HttpClientUtils.closeQuietly(this.httpClient);
    }
}
